package ru.spbau.design.snmp.android.viewer.model;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import ru.spbau.design.snmp.android.viewer.utils.ReplaceableListItem;

public final class NetworkSnapshotXMLParser {

	public NetworkSnapshot parseXML(Document doc) throws ModelException {

		StringBuilder errs = new StringBuilder();
		try {
			NetworkSnapshot netSnapshot = new NetworkSnapshot();
			Element rootXMLNode = doc.getDocumentElement();
			NodeList networksXML = rootXMLNode.getElementsByTagName("network");

			for (int networkCounter = 0; networkCounter < networksXML
					.getLength(); ++networkCounter) {

				Node networkXMLNode = networksXML.item(networkCounter);
				parseNetwork(networkXMLNode, netSnapshot, errs);
			}
			if (errs.length() != 0) {
				throw new ModelException(errs.toString());
			}
			return netSnapshot;
		} catch (Exception e) {
			throw new ModelException(e);
		}
	}

	private void parseNetwork(Node networkXMLNode, NetworkSnapshot netSnapshot,
			StringBuilder errs) {

		String networkIdStr = getStringAttribute(networkXMLNode, "id");
		if (networkIdStr.equals("")) {
			errs.append("Network has empty id, scipping Network\n");
			return;
		}
		Network network = new Network(networkIdStr);

		NodeList subNetworksXML = ((Element) networkXMLNode)
				.getElementsByTagName("subnet");
		parseSubNets(network, subNetworksXML, errs);

		NodeList routersXML = ((Element) networkXMLNode)
				.getElementsByTagName("router");
		parseRouters(network, routersXML, errs);
		netSnapshot.addNetwork(network);

	}

	private void parseRouters(Network network, NodeList routersXML,
			StringBuilder errs) {

		for (int routerCounter = 0; routerCounter < routersXML.getLength(); ++routerCounter) {
			Node routerXMLNode = routersXML.item(routerCounter);
			parseRouter(network, routerXMLNode, errs);
		}
	}

	private void parseRouter(Network network, Node routerXMLNode,
			StringBuilder errs) {
		Router router = new Router(getStringAttribute(routerXMLNode, "descr"),
				network);

		NodeList interfacesXML = ((Element) routerXMLNode)
				.getElementsByTagName("interface");
		parseRouterInterfaces(network, router, interfacesXML, errs);
		network.addRouter(router);
	}

	private void parseRouterInterfaces(Network network, Router router,
			NodeList interfacesXML, StringBuilder errs) {

		for (int interfaceCounter = 0; interfaceCounter < interfacesXML
				.getLength(); ++interfaceCounter) {
			Node interfaceXMLNode = interfacesXML.item(interfaceCounter);
			parseRouterInterface(network, router, interfaceXMLNode, errs);
		}
	}

	private void parseRouterInterface(Network network, Router router,
			Node interfaceXMLNode, StringBuilder errs) {
		IPAddress interfaceAddr = null;
		try {
			interfaceAddr = new IPAddress(getStringAttribute(interfaceXMLNode,
					"ip"), getStringAttribute(interfaceXMLNode, "mask"));
		} catch (ModelException e) {
			errs.append("Router interface IP address is invalid, "
					+ "scipping NetInteface\n");
			return;
		}

		SubNet interfaceSubNet = network.getSubNetById(getStringAttribute(
				interfaceXMLNode, "subnet-id"));

		if (interfaceSubNet == null) {
			errs.append("Couldn't find SubNetWith id "
					+ getStringAttribute(interfaceXMLNode, "subnet-id")
					+ " for NetInterface " + interfaceAddr.getAddressStr()
					+ " of router " + router.getDescription() + ".\n"
					+ "Scipping NetInterface\n");
			return;
		}

		String interfaceName = getStringAttribute(interfaceXMLNode, "name");

		NetNodeRouterInterface interfaceNetNode = new NetNodeRouterInterface(
				interfaceAddr, interfaceName, interfaceSubNet, router);

		ReplaceableListItem<NetNode> interfaceNetNodeOld = interfaceSubNet
				.getNetNodeByIP(interfaceAddr);
		if (interfaceNetNodeOld == null) {
			errs.append("Couldn't find NetNode with address "
					+ interfaceAddr.getAddressStr() + " of NetInterface "
					+ interfaceAddr.getAddressStr() + " of router "
					+ router.getDescription() + ".\n"
					+ "Scipping NetInterface\n");
			return;
		}
		interfaceNetNodeOld.replaceItem(interfaceNetNode);

		NetInterface netInterface = new NetInterface(interfaceNetNode,
				interfaceSubNet, getIntAttribute(interfaceXMLNode, "inBytes"),
				getIntAttribute(interfaceXMLNode, "outBytes"), router);
		router.addInterface(netInterface);
		interfaceSubNet.addRouterInterface(netInterface);
	}

	private void parseSubNets(Network network, NodeList subNetworksXML,
			StringBuilder errs) {

		for (int subNetCounter = 0; subNetCounter < subNetworksXML.getLength(); ++subNetCounter) {
			Node subNetXMLNode = subNetworksXML.item(subNetCounter);
			parseSubNet(subNetXMLNode, network, errs);
		}
	}

	private void parseSubNet(Node subNetXMLNode, Network network,
			StringBuilder errs) {
		String subNetIdStr = getStringAttribute(subNetXMLNode, "id");
		if (subNetIdStr.equals("")) {
			errs.append("Subnet id is empty, scipping SubNet\n");
			return;
		}
		IPAddress subNetMask = null;
		try {
			subNetMask = new IPAddress(
					getStringAttribute(subNetXMLNode, "mask"));
		} catch (ModelException e) {
			errs.append("Subnet mask is invalid, scipping SubNet\n");
			return;
		}

		SubNet subNet = new SubNet(subNetIdStr, subNetMask, network);

		NodeList netNodesXML = ((Element) subNetXMLNode)
				.getElementsByTagName("node");
		for (int netNodeCounter = 0; netNodeCounter < netNodesXML.getLength(); ++netNodeCounter) {

			Node netNodeXMLNode = netNodesXML.item(netNodeCounter);
			parseNetNode(subNet, netNodeXMLNode, errs);
		}
		network.addSubNet(subNet);
	}

	private void parseNetNode(SubNet subNet, Node netNodeXMLNode,
			StringBuilder errs) {
		IPAddress nodeAddress = null;
		try {
			nodeAddress = new IPAddress(
					getStringAttribute(netNodeXMLNode, "ip"));
		} catch (ModelException ex) {
			errs.append("NetNode ip address is invalid, scipping NetNode\n");
			return;
		}
		NetNode netNode = new NetNodeComputer(nodeAddress, "", subNet);
		subNet.addNode(netNode);
	}
	/* If we needed strict functions which don't produce 'empty value'
	 * but throw exception we would have written them. */
	private static String getStringAttribute(Node node, String attrName) {
		NamedNodeMap nodeXMLAttributes = node.getAttributes();
		String attrStr = "";
		try {
			attrStr = nodeXMLAttributes.getNamedItem(attrName).getNodeValue();
		} catch (NullPointerException ex) {
			// simply return empty string if no attribute
			// upper layer will check string for emptiness if needed
		}
		return attrStr;
	}

	private static int getIntAttribute(Node node, String attrName) {
		String attrStr = getStringAttribute(node, attrName);
		try {
			return Integer.parseInt(attrStr);
		} catch (NumberFormatException ex) {
			return 0; // returning zero is enough for us
		}
	}
}
